#include <unistd.h>
#include <fcntl.h>
#include "seqiot.h"
#include "sqlite.h"
#include "daemon.h"
#include "z_memory.h"

#define SQL_DAEMON_COLS "id, name, is_initializable, is_restartable, is_singleton, is_service" \
", path, bin, args, pid_file, status_cmd, start_cmd, stop_cmd, depend_id, chk_cycle" \
", p1_times, p1_cmd, p2_times, p2_cmd, p3_times, p3_cmd"

daemon_t *daemon_new() {
    return z_calloc(1, sizeof(daemon_t));
}

void daemon_free(daemon_t *ptr) {
    if (ptr) z_free(ptr);
}

void read_row_daemon(sqlite3_stmt *stmt, daemon_t *rs) {
    int idx = 2;
    read_row_object(stmt, &rs->super);

    rs->is_init = sqlite3_column_int(stmt, idx++);
    rs->is_restart = sqlite3_column_int(stmt, idx++);
    rs->is_singleton = sqlite3_column_int(stmt, idx++);
    rs->is_service = sqlite3_column_int(stmt, idx++);

    read_row_str(stmt, idx++, rs->path, sizeof(rs->path));
    read_row_str(stmt, idx++, rs->bin, sizeof(rs->bin));
    read_row_str(stmt, idx++, rs->args, sizeof(rs->args));
    snprintf(rs->_file, sizeof(rs->_file), "%s/%s", rs->path, rs->bin);

    read_row_str(stmt, idx++, rs->pid_file, sizeof(rs->pid_file));

    read_row_str(stmt, idx++, rs->status_cmd, sizeof(rs->status_cmd));
    read_row_str(stmt, idx++, rs->start_cmd, sizeof(rs->start_cmd));
    read_row_str(stmt, idx++, rs->stop_cmd, sizeof(rs->stop_cmd));

    rs->depend_id = sqlite3_column_int(stmt, idx++);
    rs->chk_cycle = sqlite3_column_int(stmt, idx++);

    rs->p1_times = sqlite3_column_int(stmt, idx++);
    read_row_str(stmt, idx++, rs->p1_cmd, sizeof(rs->p1_cmd));

    rs->p2_times = sqlite3_column_int(stmt, idx++);
    read_row_str(stmt, idx++, rs->p2_cmd, sizeof(rs->p2_cmd));

    rs->p3_times = sqlite3_column_int(stmt, idx++);
    read_row_str(stmt, idx++, rs->p3_cmd, sizeof(rs->p3_cmd));
}

int read_stmt_daemons(sqlite3 *db, vector_t *daemons) {
    int rc;
    sqlite3_stmt *stmt = NULL;
    char sql[1024];

    sprintf(sql, "SELECT %s FROM daemon WHERE %s", SQL_DAEMON_COLS, SQL_DISABLE);
    rc = sqlite3_prepare(db, sql, -1, &stmt, NULL);
    if (SQLITE_OK != rc) {
        sqlite_check(db);
        goto err;
    }

    while (SQLITE_ROW == sqlite3_step(stmt)) {
        daemon_t *daemon = daemon_new();
        read_row_daemon(stmt, daemon);
        vector_add(daemons, daemon);
    }

    err:
    sqlite3_finalize(stmt);
    return rc;
}

static ssize_t _read_line(const char *filename, char *line, size_t size) {
    int fd;
    ssize_t len = 0;

    fd = open(filename, O_RDONLY);
    if (fd < 0) goto end;

    len = read(fd, line, size);

    //0替换为空格
    for (int i = 0; i < len - 1; ++i) {
        if ('\0' == line[i]) {
            line[i] = ' ';
        }
    }

    end:
    close(fd);
    return len;
}

void daemon_find_pid_by_proc(vector_t *daemons) {
    DIR *dir = opendir("/proc");
    struct dirent *next;
    while (dir && (next = readdir(dir))) {
        if (DT_DIR == next->d_type) {
            int pid = atoi(next->d_name);

            if (0 != pid) {
                char filename[MAXNAMLEN];
                char link_buf[MAXPATHLEN] = {};

                sprintf(filename, "/proc/%d/exe", pid);
                if (readlink(filename, link_buf, sizeof(link_buf)) > 0) {
                    for (int i = 0; i < vector_size(daemons); ++i) {
                        daemon_t *daemon = vector_get(daemons, i);
                        //不通过pid文件检测
                        if (!daemon->pid_file[0] && !strcmp(link_buf, daemon->_file)) {
                            if (!daemon->is_singleton) {
                                char cmd_buf[MAXPATHLEN] = {};

                                //非单例模式，需要检查命令行参数是否匹配
                                sprintf(filename, "/proc/%d/cmdline", pid);
                                ssize_t len = _read_line(filename, cmd_buf, sizeof(cmd_buf));

                                char cmd[MAXPATHLEN];
                                sprintf(cmd, "%s/%s %s", daemon->path, daemon->bin, daemon->args);
                                if (!strncmp(cmd, cmd_buf, len)) {
                                    daemon->_pid = pid;
                                }
                            } else {
                                daemon->_pid = pid;
                            }
                        }
                    }
                }
            }
        }
    }
    closedir(dir);
}

void daemon_find_pid_by_file(vector_t *daemons) {
    for (int i = 0; i < vector_size(daemons); ++i) {
        daemon_t *daemon = vector_get(daemons, i);
        if (daemon->pid_file[0]) {
            char buf[80];
            int size = _read_line(daemon->pid_file, buf, sizeof(buf));
            if (size > 0) {
                daemon->_pid = atoi(buf);
            } else {
                daemon->_pid = 0;
            }
        }
    }
}

_Bool daemon_active(daemon_t *daemon) {
    daemon->_active = false;

    if (0 != daemon->_pid) {
        char buf[MAXNAMLEN];
        sprintf(buf, "/proc/%d", daemon->_pid);
        daemon->_active = 0 == access(buf, F_OK);
    } else if (daemon->status_cmd[0]) {
        log_info("check active by `%s`", daemon->status_cmd);
        daemon->_active = 0 == system(daemon->status_cmd);
    }

    if (daemon->_active) {
        daemon->_fail_counter = 0;
    } else {
        daemon->_fail_counter++;
    }

    return daemon->_active;
}

int daemon_restart(daemon_t *p_daemon) {
    int rc = 0;

    p_daemon->_counter++;
    if (p_daemon->is_service) {
        if (p_daemon->stop_cmd[0]) {
            rc = system(p_daemon->stop_cmd);
            if (0 == rc) {
                log_info("`%s` stop successful", p_daemon->stop_cmd);
            } else {
                log_error("`%s` stop failed", p_daemon->stop_cmd);
            }
        }
        if (p_daemon->start_cmd[0]) {
            rc = system(p_daemon->start_cmd);
            if (0 == rc) {
                log_info("`%s` start successful", p_daemon->start_cmd);
            } else {
                log_error("`%s` start failed", p_daemon->start_cmd);
            }
        }
    } else {
        if (p_daemon->bin[0]) {
            char cmdline[PATH_MAX];

            if (0 != p_daemon->_pid) {
                snprintf(cmdline, sizeof(cmdline), "kill %d", p_daemon->_pid);
                system(cmdline);//终止进程
            }

            snprintf(cmdline, sizeof(cmdline), "%s/%s %s 1>/dev/null &",
                     p_daemon->path, p_daemon->bin, p_daemon->args);

            rc = system(cmdline);//启动进程
            if (0 == rc) {
                log_info("`%s` run successful", cmdline);
            } else {
                log_error("`%s` run failed", cmdline);
            }
        }
    }

    return rc;
}

void daemon_check(daemon_t *p_daemon) {
    daemon_active(p_daemon);
    if (!p_daemon->_active) {
        int rc;
        if (0 != p_daemon->p3_times && p_daemon->_fail_counter > p_daemon->p3_times) {
            log_info("exec p3 `%s` after %d retries", p_daemon->p3_cmd, p_daemon->p3_times);
            rc = system(p_daemon->p3_cmd);
            if (rc != 0) {
                log_error("p3's cmd %s failed.", p_daemon->p3_cmd);
            }
        } else if (0 != p_daemon->p2_times && p_daemon->_fail_counter > p_daemon->p2_times) {
            log_info("exec p2 `%s` after %d retries", p_daemon->p2_cmd, p_daemon->p2_times);
            rc = system(p_daemon->p2_cmd);
            if (rc != 0) {
                log_error("p2's cmd %s failed.", p_daemon->p2_cmd);
            }
        } else if (0 != p_daemon->p1_times && p_daemon->_fail_counter > p_daemon->p1_times) {
            log_info("exec p1 `%s` after %d retries", p_daemon->p1_cmd, p_daemon->p1_times);
            rc = system(p_daemon->p1_cmd);
            if (rc != 0) {
                log_error("p1's cmd %s failed.", p_daemon->p1_cmd);
            }
        }
        if (p_daemon->is_restart) {
            daemon_restart(p_daemon);
        }
    } else {
        p_daemon->_fail_counter = 0;
    }
}